#!/usr/bin/env python3

# Copyright (c) 2020-2021 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import os
import sys
import signal
import socket
from optparse import OptionParser

# establish the eprefix, initially set so eprefixify can
# set it on install
EPREFIX = "@GENTOO_PORTAGE_EPREFIX@"

# check and set it if it wasn't
if "GENTOO_PORTAGE_EPREFIX" in EPREFIX:
    EPREFIX = ''


class MirrorSelect(object):
	'''Main operational class'''

	def __init__(self, output=None):
		'''MirrorSelect class init

		@param output: mirrorselect.output.Ouptut() class instance
			or None for the default instance
		'''
		self.output = output or Output()


	@staticmethod
	def _have_bin(name):
		"""Determines whether a particular binary is available
		on the host system.  It searches in the PATH environment
		variable paths.

		@param name: string, binary name to search for
		@rtype: string or None
		"""
		for path_dir in os.environ.get("PATH", "").split(":"):
			if not path_dir:
				continue
			file_path = os.path.join(path_dir, name)
			if os.path.isfile(file_path) and os.access(file_path, os.X_OK):
				return file_path
		return None


	def change_config(self, hosts, out, config_path, sync=False):
		"""Writes the config changes to the given file, or to stdout.

		@param hosts: list of host urls to write
		@param out: boolean, used to redirect output to stdout
		@param config_path; string
		@param sync: boolean, used to switch between sync-uri repos.conf target,
			SYNC and GENTOO_MIRRORS make.conf variable target
		"""
        pass

	@staticmethod
	def write_to_output(mirror_string):
		print()
		print(mirror_string)
		sys.exit(0)


	def _parse_args(self, argv):
		"""
		Does argument parsing and some sanity checks.
		Returns an optparse Options object.

		The descriptions, grouping, and possibly the amount sanity checking
		need some finishing touches.
		"""
		desc = "\n".join((
			self.output.white("examples:"),
			"",
			self.output.white("	 automatic:"),
			"		 # mirrorselect -s5",
			"		 # mirrorselect -s3 -b10 -o >> /mnt/gentoo/etc/portage/make.conf",
			"		 # mirrorselect -D -s4",
			"",
			self.output.white("	 interactive:"),
			"		 # mirrorselect -i -r",
			))
		parser = OptionParser(
			formatter=ColoredFormatter(self.output), description=desc,
			version='Mirrorselect version: %s' % version)

		group = parser.add_option_group("Main modes")
		group.add_option(
			"-D", "--deep", action="store_true", default=False,
			help="Deep mode. This is used to give a more accurate "
			"speed test. It will download a 100k file from "
			"each server. Because of this you should only use "
			"this option if you have a good connection.")
		group.add_option(
			"-i", "--interactive", action="store_true", default=False,
			help="Interactive Mode, this will present a list "
			"to make it possible to select mirrors you wish to use.")

		group = parser.add_option_group(
			"Server type selection (choose at most one)")
		group.add_option(
			"-c", "--country", action="store", default=None,
			help="only use mirrors from the specified country "
			"NOTE: Names with a space must be quoted "
			"eg.:  -c 'South Korea'")
		group.add_option(
			"-R", "--region", action="store", default=None,
			help="only use mirrors from the specified region "
			"NOTE: Names with a space must be quoted "
			"eg.:  -R 'North America'")

		group = parser.add_option_group("Other options")
		group.add_option(
			"-b", "--blocksize", action="store", type="int",
			help="This is to be used in automatic mode "
			"and will split the hosts into blocks of BLOCKSIZE for "
			"use with netselect. This is required for certain "
			"routers which block 40+ requests at any given time. "
			"Recommended parameters to pass are: -s3 -b10")
		group.add_option(
			"-d", "--debug", action="store", type="int", dest="verbosity",
			default=1, help="debug mode, pass in the debug level [1-9]")
		group.add_option(
			"-q", "--quiet", action="store_const", const=0, dest="verbosity",
			help="Quiet mode")
		group.add_option(
			"-t", "--timeout", action="store", type="int",
			default="10", help="Timeout for deep mode. Defaults to 10 seconds.")
		group.add_option(
			"-e", "--exclude", action="append", dest="exclude",
			default=None, help="Exclude host from mirrors list.")

		if len(argv) == 1:
			parser.print_help()
			sys.exit(1)

		options, args = parser.parse_args(argv[1:])

		# sanity checks

		if options.ipv4 and options.ipv6:
			self.output.print_err('Choose at most one of --ipv4 and --ipv6')

		if (options.ipv6 and not socket.has_ipv6) and not options.interactive:
			options.ipv6 = False
			self.output.print_err('The --ipv6 option requires python ipv6 support')

		if options.interactive and (
			options.deep or
			options.blocksize):
			self.output.print_err('Invalid option combination with -i')

		if args:
			self.output.print_err('Unexpected arguments passed.')

		# return results
		return options


	def get_available_hosts(self, options):
		'''Returns a list of hosts suitable for consideration by a user
		based on user input

		@param options: parser.parse_args() options instance
		@rtype: list
		'''
		if options.rsync:
			self.output.write("using url: %s\n" % MIRRORS_RSYNC_DATA, 2)
			hosts = Extractor(MIRRORS_RSYNC_DATA, options, self.output).hosts
		else:
			self.output.write("using url: %s\n" % MIRRORS_3_XML, 2)
			hosts = Extractor(MIRRORS_3_XML, options, self.output).hosts

		if options.exclude:
			hosts = [x for x in hosts if x[0] not in options.exclude]

		return hosts


	def select_urls(self, hosts, options):
		'''Returns the list of selected host urls using
		the options passed in to run one of the three selector types.
		1) Interactive ncurses dialog
		2) Deep mode mirror selection.
		3) (Shallow) Rapid server selection via netselect

		@param hosts: list of hosts to choose from
		@param options: parser.parse_args() options instance
		@rtype: list
		'''
		if options.interactive:
			selector = Interactive(hosts, options, self.output)
		elif options.deep:
			selector = Deep(hosts, options, self.output)
		else:
			selector = Shallow(hosts, options, self.output)
		return selector.urls

	def main(self, argv):
		"""Lets Rock!

		@param argv: list of command line arguments to parse
		"""

		options = self._parse_args(argv)
		self.output.verbosity = options.verbosity

		hosts = self.get_available_hosts(options)

		if options.all_mirrors:
			urls = sorted([url for url, args in list(hosts)])
			if options.rsync:
				urls = [urls[0]]
		else:
			urls = self.select_urls(hosts, options)

		if len(urls):
			self.change_config(urls)
		else:
			self.output.write("No search results found. "
				"Check your filter settings and re-run mirrorselect\n")

































if __name__ == "__main__":
    # This block ensures that ^C interrupts are handled quietly.
    try:
        def exithandler(signum, frame):
            signal.signal(signal.SIGINT, signal.SIG_IGN)
            signal.signal(signal.SIGTERM, signal.SIG_IGN)
            print('Caught signal %s. Exiting' % (signum))
            sys.exit(1)

        signal.signal(signal.SIGINT, exithandler)
        signal.signal(signal.SIGTERM, exithandler)
        signal.signal(signal.SIGPIPE, signal.SIG_DFL)
    except KeyboardInterrupt:
        sys.exit(1)

    # Lets Rock!
    try:
        MirrorSelect().main(sys.argv)
    except KeyboardInterrupt:
        print("Aborted.")
        sys.exit(130)
